工具使用集

您所在的位置:网站首页 resttemplate 获取token 工具使用集

工具使用集

2023-05-10 04:13| 来源: 网络整理| 查看: 265

远程调用工具 前言

在工作中,除了通过切面的方式来记录调用日志或者通过MVc提供的Filter来进行操作,还有什么别的方式嘛?远程调用工具自身携带一些类可以实现一些类似的功能。记录多种远程调用工具的使用。想让自己对它们有一个整体的认识,想自己可以顺手的使用他们。

WebClient

非阻塞和响应式:WebClient 基于 Reactor 提供了非阻塞的编程模型,可以处理大量并发请求,减少线程的阻塞等待时间,并提供了响应式的操作符和数据流处理。

非阻塞指的是在 IO 操作中,线程不会被阻塞等待操作完成,可以继续执行其他任务。而响应式是一种基于事件流的编程范式,关注数据流的处理和转换。

异步和同步请求:WebClient 支持异步和同步的请求方式。使用异步方式,可以通过返回 Mono(单个值)或 Flux(多个值)来处理响应。而同步方式会阻塞当前线程,直到收到完整的响应。

同步请求:

发起请求后,当前线程会阻塞等待服务器响应。 在收到完整的响应后,才会继续执行后续代码。 适用于简单的请求场景,其中请求和响应之间的关系是一对一的。 请求处理和响应处理是串行进行的。

异步请求:

发起请求后,当前线程不会阻塞等待服务器响应。 可以通过回调函数、Future/Promise 或响应式编程等方式,注册回调来处理响应或获取响应结果。 适用于同时发起多个请求、并行处理多个请求或不需要立即等待响应的场景。 请求处理和响应处理是并行进行的。

Mono 表示一个包含零个或一个元素的异步序列。可以将它理解为一个单值的异步结果,类似于 Java 8 中的 CompletableFuture。Mono 在处理单个值的情况下非常有用,例如获取单个资源或执行单个操作。

Flux 表示一个包含零个或多个元素的异步序列。可以将它理解为一个多值的异步结果,类似于 Java 8 中的 Stream。Flux 在处理多个值的情况下非常有用,例如处理多个资源或批量操作。

功能丰富的 API:WebClient 提供了丰富的方法和操作符来构建和处理 HTTP 请求和响应。你可以设置请求方法、添加请求头、设置请求体、处理响应状态码、处理响应体等。

发起请求:

使用 get()、post()、put()、delete() 等方法来指定 HTTP 方法。 可以使用 uri() 方法设置请求的 URI。 通过 headers() 方法设置请求头。 可以使用 body() 方法设置请求体。

发送请求并处理响应:

使用 exchange() 方法发送请求并获取响应。 使用 retrieve() 方法获取响应体,可以通过 toEntity()、toFlux()、toMono() 等方法将响应转换为不同的数据类型。 可以使用 bodyToXxx() 方法将响应体转换为指定的数据类型,如 bodyToMono()、bodyToFlux()、bodyToEntity() 等。

异常处理:

使用 onStatus() 方法可以处理特定的 HTTP 状态码,并进行相应的处理逻辑。 可以使用 onError() 方法处理请求过程中的错误情况。 通过 onStatus()、onError() 等方法可以使用操作符链式组合多个异常处理逻辑。

响应处理:

使用操作符(如 map()、flatMap()、filter() 等)对响应进行处理和转换。 可以使用操作符进行响应流的合并、转换、过滤、排序等操作。 可以使用 doOnXxx() 方法对响应进行副作用操作,如记录日志、统计信息等。

超时和重试:

使用 timeout() 方法可以设置请求的超时时间。 可以使用 retry() 方法进行请求重试,可以自定义重试条件和重试策略。

文件上传和下载:

使用 multipart() 方法可以进行文件上传,支持单个文件或多个文件的上传。 可以使用 body(BodyInserters.fromResource()) 方法下载文件,将响应体保存为文件。

请求拦截器和过滤器:

可以通过 filter() 方法添加请求拦截器,对请求进行预处理或修改请求参数。 通过自定义 ExchangeFilterFunction 可以添加全局的请求过滤器,对所有请求进行统一的处理。 import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; ​ public class GlobalRequestFilterExample { ​    public static void main(String[] args) {        WebClient webClient = WebClient.builder()               .baseUrl("https://api.example.com")               .filter(globalRequestFilter())               .build(); ​        webClient.get()               .uri("/users")               .retrieve()               .toEntity(String.class)               .subscribe(response -> {                    HttpStatus statusCode = response.getStatusCode();                    HttpHeaders headers = response.getHeaders();                    String body = response.getBody();                    System.out.println("Status: " + statusCode);                    System.out.println("Headers: " + headers);                    System.out.println("Body: " + body);               });   } ​    private static ExchangeFilterFunction globalRequestFilter() {        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {            ClientRequest.Builder builder = ClientRequest.from(clientRequest);            // 添加全局请求头            builder.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);            // 修改请求方法            builder.method(HttpMethod.GET); ​            // 返回修改后的请求            return Mono.just(builder.build());       });   } } ​ 复制代码

数据转换和编解码:WebClient 支持数据转换,可以将请求和响应体转换为各种数据类型,如 JSON、XML 等。它使用 Jackson、Gson、XML 编解码器等来实现数据的序列化和反序列化。

响应体的数据类型转换:

使用 toEntity() 方法将响应体转换为 ResponseEntity 对象,包含了响应的状态码、响应头和响应体。 可以使用 toFlux() 方法将响应体转换为 Flux 对象,用于处理响应体为多个元素的情况。 可以使用 toMono() 方法将响应体转换为 Mono 对象,用于处理响应体为单个元素的情况。

响应体的数据格式转换:

WebClient 支持将响应体从 JSON、XML、HTML、文本等格式转换为对象或字符串。 可以使用 bodyToXxx() 方法将响应体转换为指定的数据类型,如 bodyToMono()、bodyToFlux()、bodyToEntity() 等。 WebClient 默认集成了 JSON 和 XML 的数据转换器,可以将响应体直接转换为 Java 对象。

请求体的数据类型转换:

使用 bodyValue() 方法可以设置请求体的数据,WebClient 会根据请求的 Content-Type 进行适当的编码。 可以使用 body() 方法指定请求体的数据类型,WebClient 会根据数据类型进行编码,如 JSON、XML、表单等。 可以使用 body(BodyInserters.fromXxx()) 方法将 Java 对象转换为请求体的数据,如 body(BodyInserters.fromValue())、body(BodyInserters.fromFormData()) 等。

数据编解码器的自定义:

可以通过自定义 Encoder 和 Decoder 实现数据的自定义编解码。 WebClient 提供了 codecs() 方法来配置自定义的编解码器,可以设置 JSON、XML、表单等数据格式的编解码器。 import org.springframework.core.ResolvableType; import org.springframework.http.MediaType; import org.springframework.http.codec.json.AbstractJackson2Decoder; import org.springframework.http.codec.json.AbstractJackson2Encoder; import org.springframework.http.codec.json.Jackson2CodecSupport; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; ​ public class CustomCodecExample { ​    public static void main(String[] args) {        WebClient webClient = WebClient.builder()               .exchangeStrategies(customExchangeStrategies())               .build(); ​        webClient.get()               .uri("https://api.example.com/users")               .accept(MediaType.APPLICATION_JSON)               .retrieve()               .bodyToMono(User.class)               .subscribe(user -> {                    System.out.println("User: " + user);               });   } ​    static class User {        private String name;        private int age; ​        // 构造方法、getter 和 setter 省略 ​        @Override        public String toString() {            return "User{" +                    "name='" + name + ''' +                    ", age=" + age +                    '}';       }   } ​    private static ExchangeStrategies customExchangeStrategies() {        return ExchangeStrategies.builder()               .codecs(configurer -> {                    configurer.defaultCodecs().jackson2JsonEncoder(new CustomJsonEncoder());                    configurer.defaultCodecs().jackson2JsonDecoder(new CustomJsonDecoder());               })               .build();   } ​    private static class CustomJsonEncoder extends AbstractJackson2Encoder { ​        CustomJsonEncoder() {            super(new Jackson2CodecSupport() {                @Override                public void setSerializedType(ResolvableType type) {                    // 自定义序列化的类型                    super.setSerializedType(ResolvableType.forClass(User.class));               }           }, MediaType.APPLICATION_JSON);       }   } ​    private static class CustomJsonDecoder extends AbstractJackson2Decoder { ​        CustomJsonDecoder() {            super(new Jackson2CodecSupport() {                @Override                public void setSerializedType(ResolvableType type) {                    // 自定义反序列化的类型                    super.setSerializedType(ResolvableType.forClass(User.class));               }           }, MediaType.APPLICATION_JSON);       }   } } 复制代码

连接池管理:WebClient 内置了连接池管理,可以重用连接,减少资源消耗和连接建立的时间。

WebClient 在默认情况下使用一个共享的连接池,这意味着多个 WebClient 实例将共享同一个连接池。这样可以减少资源的占用,并提高性能。 WebClient 默认会重用连接,如果一个请求完成后仍然有其他请求需要发起,WebClient 会尝试重用现有的连接,而不是关闭它。 连接池管理还涉及连接的保持时间。当连接在一段时间内没有被使用时,连接可能会被关闭或释放。 当请求完成后,连接可以被释放回连接池以供其他请求使用。默认情况下,WebClient 会自动释放连接。 import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; ​ @Configuration public class WebClientConfig { ​    @Bean    public WebClient webClient(WebClient.Builder builder) {        // 设置连接池最大连接数和每个主机的最大连接数        builder.clientConnector(new ReactorClientHttpConnector(HttpClient.create().wiretap(true)               .tcpConfiguration(tcpClient -> tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)                       .doOnConnected(conn -> conn                               .addHandlerLast(new ReadTimeoutHandler(5))                               .addHandlerLast(new WriteTimeoutHandler(5))))).build();                // 设置连接的保持时间        builder.keepAlive(Duration.ofMinutes(5));                // 可以根据需要进行其他的配置,如数据转换器、错误处理等        builder.exchangeStrategies(ExchangeStrategies.builder()               .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))               .build());                return builder.build();   } ​    @Bean    public WebClientCustomizer webClientCustomizer() {        return webClientBuilder -> {            // 可以在这里进行全局的 WebClient 配置            webClientBuilder.baseUrl("https://api.test.com");            // ...       };   } } ​ 复制代码

错误处理:WebClient 提供了错误处理机制,可以处理请求失败、超时、错误状态码等情况,并支持自定义错误处理逻辑。

异常处理:

使用 onStatus() 方法可以指定处理特定的 HTTP 状态码,如 onStatus(HttpStatus::isError, errorHandler) 可以处理错误状态码。 使用 onError() 方法可以指定处理请求过程中的异常情况,如网络连接失败、连接超时等。

错误处理器:

使用 exchange() 方法可以获取完整的响应信息,包括状态码、响应头和响应体。 使用 bodyToMono() 或 bodyToFlux() 方法将响应体转换为相应的 Mono 或 Flux 对象。 可以使用 onErrorResume() 方法设置自定义的错误处理逻辑,返回一个备用的 Mono 或 Flux 对象。

超时处理:

使用 timeout() 方法可以设置请求的超时时间,当请求超过指定时间仍未完成时,会触发超时异常。 使用 retry() 方法可以进行请求的重试,可以设置最大重试次数和重试条件,用于处理请求失败的情况。

错误信号转换:

使用 onErrorMap() 方法可以将错误信号转换为不同的异常类型,以便在错误处理时进行更精细的判断和处理。 使用 onErrorResume() 方法可以根据不同的错误类型返回不同的备用数据,用于灵活处理不同类型的错误情况。 import org.springframework.http.HttpStatus; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; ​ public class ErrorHandlingExample { ​    public static void main(String[] args) {        WebClient webClient = WebClient.create(); ​        webClient.get()               .uri("https://api.example.com/users/123")               .retrieve()               .onStatus(HttpStatus::isError, response -> {                    if (response.statusCode() == HttpStatus.NOT_FOUND) {                        return Mono.error(new UserNotFoundException("User not found"));                   } else {                        return Mono.error(new RuntimeException("Request failed with status code: " + response.statusCode()));                   }               })               .bodyToMono(User.class)               .doOnError(UserNotFoundException.class, ex -> {                    System.out.println("User not found: " + ex.getMessage());               })               .doOnError(RuntimeException.class, ex -> {                    System.out.println("Request failed: " + ex.getMessage());               })               .subscribe(user -> {                    System.out.println("User: " + user);               });   } ​    static class User {        private String name;        private int age; ​        // 构造方法、getter 和 setter 省略 ​        @Override        public String toString() {            return "User{" +                    "name='" + name + ''' +                    ", age=" + age +                    '}';       }   } ​    static class UserNotFoundException extends RuntimeException {        public UserNotFoundException(String message) {            super(message);       }   } } ​ 复制代码

可扩展性和定制性:WebClient 可以通过配置不同的 ExchangeStrategies、过滤器、拦截器等来进行定制和扩展,以满足不同的需求。

配置过滤

import org.springframework.http.HttpStatus; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; ​ public class ErrorHandlingExample { ​    public static void main(String[] args) {        WebClient webClient = WebClient.builder()               .baseUrl("https://api.example.com")               .filter(new ErrorHandlingFilter())               .build(); ​        webClient.get()               .uri("/users/123")               .retrieve()               .bodyToMono(User.class)               .subscribe(user -> {                    System.out.println("User: " + user);               });   } ​    static class User {        private String name;        private int age; ​        // 构造方法、getter 和 setter 省略 ​        @Override        public String toString() {            return "User{" +                    "name='" + name + ''' +                    ", age=" + age +                    '}';       }   } ​    static class ErrorHandlingFilter implements ExchangeFilterFunction { ​        @Override        public Mono filter(ClientRequest request, ExchangeFunction next) {            return next.exchange(request)                   .flatMap(response -> {                        if (response.statusCode().isError()) {                            if (response.statusCode() == HttpStatus.NOT_FOUND) {                                return Mono.error(new UserNotFoundException("User not found"));                           } else {                                return Mono.error(new RuntimeException("Request failed with status code: " + response.statusCode()));                           }                       }                        return Mono.just(response);                   });       }   } ​    static class UserNotFoundException extends RuntimeException {        public UserNotFoundException(String message) {            super(message);       }   } } ​ 复制代码 实战:

添加所需的依赖到 pom.xml 文件中:

          org.springframework.boot       spring-boot-starter-webflux   ​           org.springframework.boot       spring-boot-starter-validation   ​           com.fasterxml.jackson.core       jackson-databind   复制代码

注意:

Spring WebFlux中的WebClient,它的底层实现可以使用不同的HTTP客户端,包括Apache HttpClient和OkHttp。

显式地使用Apache HttpClient作为WebClient的底层实现,可以通过添加spring-webflux-httpclient依赖来实现,同时需要排除默认的Reactor Netty依赖。

org.springframework.boot spring-boot-starter-webflux         io.projectreactor.netty       reactor-netty-http   ​ org.springframework.boot spring-boot-starter-webflux         io.projectreactor.netty       reactor-netty   ​ org.springframework.boot spring-boot-starter-webflux ​ org.springframework.boot spring-boot-starter-webflux         io.netty       netty-transport-native-epoll           io.netty       netty-transport-native-unix-common   ​ org.apache.httpcomponents httpclient ​ 复制代码

将 WebClient 配置为 @Configuration 类:

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.codec.ClientCodecConfigurer; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; ​ @Configuration public class WebClientConfig { ​    @Bean    public WebClient webClient() {        ExchangeStrategies strategies = ExchangeStrategies.builder()               .codecs(this::customCodecs)               .build(); ​        return WebClient.builder()               .exchangeStrategies(strategies)               .build();   } ​    private void customCodecs(ClientCodecConfigurer configurer) {        configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder());        configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder());        configurer.defaultCodecs().maxInMemorySize(1024 * 1024);        configurer.defaultCodecs().defaultContentType(MediaType.APPLICATION_JSON);   } } ​ 复制代码

创建一个 UserController 类,用于定义 REST API 的处理器方法:

import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; ​ @RestController @RequestMapping("/users") @Validated public class UserController { ​    private final UserService userService; ​    public UserController(UserService userService) {        this.userService = userService;   } ​    @GetMapping("/{id}")    public Mono getUserById(@PathVariable String id) {        return userService.getUserById(id);   } ​    @PostMapping    public Mono createUser(@RequestBody @Validated CreateUserRequest request) {        return userService.createUser(request);   } } ​ 复制代码

创建一个 UserService 类,用于处理用户相关的业务逻辑:

import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; ​ @Service public class UserService { ​    public Mono getUserById(String id) {        // Simulate fetching user data from external service        WebClient webClient = WebClient.create();        return webClient.get()               .uri("https://api.example.com/users/{id}", id)               .retrieve()               .bodyToMono(User.class);   } ​    public Mono createUser(CreateUserRequest request) {        // Simulate creating a user by sending a POST request        WebClient webClient = WebClient.create();        return webClient.post()               .uri("https://api.example.com/users")               .contentType(MediaType.APPLICATION_JSON)               .bodyValue(request)               .retrieve()               .bodyToMono(User.class);   } } ​ 复制代码

创建一个 User 类,作为数据模型:

public class User {    private String id;    private String name;    private int age; ​    // 构造方法、getter 和 setter 省略 ​    @Override    public String toString() {        return "User{" +                "id='" + id + ''' +                ", name='" + name + ''' +                ", age=" + age +                '}';   } } ​ 复制代码

创建一个 CreateUserRequest 类,用于接收创建用户的请求:

import javax.validation.constraints.NotEmpty; ​ public class CreateUserRequest {    @NotEmpty(message = "Name cannot be empty")    private String name;    private int age; ​    // 构造方法、getter 和 setter 省略 ​    @Override    public String toString() {        return "CreateUserRequest{" +                "name='" + name + ''' +                ", age=" + age +                '}';   } } ​ 复制代码 RestTemplate

RestTemplate 是 Spring 3.x 版本引入的,并在 Spring 5.x 版本中仍然可用。它的设计受到了传统的 Spring MVC 的影响,并提供了许多方便的方法和功能。

RestTemplate 是一个同步的、阻塞式的 HTTP 客户端,它主要以同步方式发送和接收 HTTP 请求。在默认情况下,RestTemplate 的方法会阻塞当前线程,直到收到响应或发生超时。

RestTemplate 的主要特点和功能如下:

发送 HTTP 请求:RestTemplate 提供了多种方法,例如 GET、POST、PUT、DELETE 等,用于发送不同类型的 HTTP 请求。

getForObject(): 发送 GET 请求,并将响应体转换为指定类型的对象。

User user = restTemplate.getForObject("http://example.com/api/user/{id}", User.class, 1); 复制代码

getForEntity(): 发送 GET 请求,并将响应体转换为 ResponseEntity 对象,包含响应的状态码、头部信息和响应体。

ResponseEntity response = restTemplate.getForEntity("http://example.com/api/user/{id}", User.class, 1); User user = response.getBody(); HttpStatus status = response.getStatusCode(); 复制代码

postForObject(): 发送 POST 请求,并将请求体转换为指定类型的对象。可以传递 URL 参数作为变量。

User user = restTemplate.postForObject("http://example.com/api/user", newUser, User.class); 复制代码

postForEntity(): 发送 POST 请求,并将请求体转换为 ResponseEntity 对象,包含响应的状态码、头部信息和响应体。

ResponseEntity response = restTemplate.postForEntity("http://example.com/api/user", newUser, User.class); User createdUser = response.getBody(); HttpStatus status = response.getStatusCode(); 复制代码

put(): 发送 PUT 请求,用于更新资源。可以传递 URL 参数作为变量。

restTemplate.put("http://example.com/api/user/{id}", updatedUser, 1); 复制代码

delete(): 发送 DELETE 请求,用于删除资源。可以传递 URL 参数作为变量。

restTemplate.delete("http://example.com/api/user/{id}", 1); 复制代码

exchange() 方法是 RestTemplate 提供的灵活且强大的方法,它可以发送任意类型的 HTTP 请求,并且可以自定义请求和响应的处理。

RestTemplate restTemplate = new RestTemplate(); String username = "username"; String password = "password"; String credentials = username + ":" + password; String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes()); HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Basic " + encodedCredentials); HttpEntity requestEntity = new HttpEntity(headers); ResponseEntity response = restTemplate.exchange("http://example.com/api/user/{id}", HttpMethod.GET, requestEntity, User.class, 1); Resource fileResource = new FileSystemResource("/path/to/file.jpg"); MultiValueMap body = new LinkedMultiValueMap(); body.add("file", fileResource); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); HttpEntity requestEntity = new HttpEntity(body, headers); ResponseEntity response = restTemplate.exchange("http://example.com/api/upload", HttpMethod.POST, requestEntity, String.class); 复制代码

请求和响应处理:RestTemplate 支持将请求参数设置为 URL 查询参数、表单参数或 JSON 请求体。它还能够将响应转换为对象或者是原始的 HTTP 响应数据。

请求参数的设置:

将参数作为 URL 查询参数:可以通过将参数拼接到 URL 中来设置查询参数。例如:http://example.com/api/user?id=123. 将参数作为表单参数:可以使用 MultiValueMap 或 Map 对象作为请求体中的表单参数。例如: MultiValueMap formData = new LinkedMultiValueMap(); formData.add("username", "john"); formData.add("password", "secret"); 复制代码 将参数作为 JSON 请求体:可以将对象转换为 JSON 字符串,并将其作为请求体发送。例如: HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); User user = new User("john", "secret"); HttpEntity requestEntity = new HttpEntity(user, headers); 复制代码

请求体的处理:

设置请求体的类型和编码:可以通过设置请求头的 Content-Type 来指定请求体的类型和编码。例如:headers.setContentType(MediaType.APPLICATION_JSON). 将请求体转换为 JSON 字符串:可以使用 JSON 库将对象转换为 JSON 字符串,并将其作为请求体发送。

响应的处理:

将响应转换为对象:可以使用 exchange() 或 getForObject() 方法将响应转换为指定的对象类型。例如:User user = restTemplate.getForObject("http://example.com/api/user/{id}", User.class, 1). 获取原始的 HTTP 响应数据:可以使用 exchange() 方法获取包含响应状态码、响应头和响应体的 ResponseEntity 对象。例如: ResponseEntity response = restTemplate.getForEntity("http://example.com/api/user/{id}", String.class, 1); String responseBody = response.getBody(); 复制代码

错误处理:RestTemplate 可以处理请求过程中发生的错误,包括网络异常、HTTP 状态码错误等。可以根据需要自定义错误处理逻辑。

抛出异常:

try {    ResponseEntity response = restTemplate.getForEntity("http://example.com/api/resource", String.class);    // 处理成功响应 } catch (RestClientException ex) {    // 处理异常情况    if (ex instanceof HttpClientErrorException) {        HttpClientErrorException httpClientErrorException = (HttpClientErrorException) ex;        HttpStatus statusCode = httpClientErrorException.getStatusCode();        // 处理特定的 HTTP 状态码错误   } else if (ex instanceof HttpServerErrorException) {        HttpServerErrorException httpServerErrorException = (HttpServerErrorException) ex;        HttpStatus statusCode = httpServerErrorException.getStatusCode();        // 处理服务器端错误   } else if (ex instanceof ResourceAccessException) {        ResourceAccessException resourceAccessException = (ResourceAccessException) ex;        // 处理网络异常等其他错误   } else {        // 处理其他类型的异常   } } 复制代码

自定义异常处理器:

public class CustomRestTemplate extends RestTemplate {    public CustomRestTemplate() {        setErrorHandler(new CustomResponseErrorHandler());   } } ​ public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {    @Override    protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {        if (statusCode.is4xxClientError()) {            // 处理客户端错误       } else if (statusCode.is5xxServerError()) {            // 处理服务器端错误       } else {            // 处理其他类型的错误       }   } } ​ 复制代码

自定义了一个 CustomRestTemplate 类,继承自 RestTemplate,并在构造函数中设置了自定义的异常处理器 CustomResponseErrorHandler。在自定义异常处理器中,我们可以根据不同的 HTTP 状态码来处理对应的错误情况。

URI 变量替换:RestTemplate 支持将 URI 中的占位符替换为实际的值,以便动态构建请求 URL。

RestTemplate restTemplate = new RestTemplate(); ​ // 定义 URI 模板,其中 {id} 是占位符 String uriTemplate = "http://example.com/api/resource/{id}"; ​ // 创建 URI 变量,设置实际的值 Map uriVariables = new HashMap(); uriVariables.put("id", "123"); ​ // 使用 URI.expand() 方法进行 URI 变量替换 URI uri = UriComponentsBuilder.fromUriString(uriTemplate)       .buildAndExpand(uriVariables)       .toUri(); ​ // 发送 GET 请求 ResponseEntity response = restTemplate.getForEntity(uri, String.class); ​ 复制代码

拦截器和过滤器:RestTemplate 允许注册请求和响应的拦截器和过滤器,用于在发送请求前和处理响应时进行额外的处理。

拦截器(Interceptor)是一种针对请求和响应的拦截处理机制,可以在发送请求前和处理响应时执行预定义的逻辑。你可以实现 ClientHttpRequestInterceptor 接口,并将其注册到 RestTemplate 中的 interceptors 集合中,以便在请求发送之前和响应处理之后进行拦截。

RestTemplate restTemplate = new RestTemplate(); ​ restTemplate.setInterceptors(List.of(new LoggingClientHttpRequestInterceptor())); ​ ​ public class LoggingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { ​    @Override    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)            throws IOException {        // 在发送请求前的拦截处理        logRequest(request, body); ​        // 继续执行请求        ClientHttpResponse response = execution.execute(request, body); ​        // 在处理响应后的拦截处理        logResponse(response); ​        return response;   } ​    private void logRequest(HttpRequest request, byte[] body) {        // 记录请求日志的逻辑        System.out.println("Sending request: " + request.getMethod() + " " + request.getURI());        System.out.println("Request body: " + new String(body, StandardCharsets.UTF_8));   } ​    private void logResponse(ClientHttpResponse response) throws IOException {        // 记录响应日志的逻辑        System.out.println("Received response: " + response.getStatusCode());        System.out.println("Response body: " + StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8));   } } ​ 复制代码

过滤器(Filter)是一种更细粒度的请求处理机制,它可以在请求的各个阶段添加自定义逻辑。

可以通过使用 ClientHttpRequestFactory 来注册过滤器。下面是一个示例,展示如何实现一个简单的过滤器来添加请求头信息:

public class HeaderFilter extends OncePerRequestFilter { ​    @Override    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)            throws ServletException, IOException {        // 添加自定义的请求头        request.addHeader("X-Request-Id", UUID.randomUUID().toString()); ​        // 继续执行过滤器链        filterChain.doFilter(request, response);   } } ​ 复制代码

创建了一个名为 HeaderFilter 的过滤器类,继承了 Spring Framework 提供的 OncePerRequestFilter 抽象类。在 doFilterInternal() 方法中,我们添加了一个自定义的请求头,将一个随机生成的请求 ID 添加到请求中。

需要创建一个自定义的 ClientHttpRequestFactory,并在其中注册过滤器。下面是一个示例:

public class CustomHttpRequestFactory extends SimpleClientHttpRequestFactory { ​    private final List filters; ​    public CustomHttpRequestFactory(List filters) {        this.filters = filters;   } ​    @Override    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {        if (filters != null && !filters.isEmpty()) {            for (Filter filter : filters) {                if (filter instanceof OncePerRequestFilter) {                    OncePerRequestFilter oncePerRequestFilter = (OncePerRequestFilter) filter;                    oncePerRequestFilter.doFilter(null, null, null);               }           }       }        super.prepareConnection(connection, httpMethod);   } } ​ 复制代码

创建了一个名为 CustomHttpRequestFactory 的自定义请求工厂类,继承了 SimpleClientHttpRequestFactory。在 prepareConnection() 方法中,我们遍历注册的过滤器列表,并调用过滤器的 doFilter() 方法进行处理。

使用这个自定义请求工厂,可以在创建 RestTemplate 实例时进行设置,示例如下:

List filters = List.of(new HeaderFilter()); RestTemplate restTemplate = new RestTemplate(new CustomHttpRequestFactory(filters)); ​ 复制代码

引申一下基于 spring 的过滤

使用 javax.servlet.Filter 接口来创建过滤器:

@WebFilter(urlPatterns = "/*") //通过注解的方式 public class HeaderFilter implements Filter { ​ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)       throws IOException, ServletException {   HttpServletRequest httpRequest = (HttpServletRequest) request;   HttpServletResponse httpResponse = (HttpServletResponse) response; ​   // 添加自定义的请求头   httpRequest.addHeader("X-Request-Id", UUID.randomUUID().toString()); ​   // 继续执行过滤器链   chain.doFilter(httpRequest, httpResponse); } ​ // 其他方法... } ​ 复制代码

web.xml 配置

​   headerFilter   com.example.HeaderFilter ​   headerFilter   /* ​ ​ 复制代码 实战:

使用 RestTemplate 进行 GET 请求,并添加自定义的请求头、处理响应结果以及错误处理:

@Configuration public class RestTemplateConfig { ​    @Bean    public RestTemplate restTemplate() {        RestTemplate restTemplate = new RestTemplate(); ​        // 添加自定义的请求拦截器        restTemplate.getInterceptors().add(new CustomHeaderInterceptor()); ​        // 设置连接超时时间和读取超时时间        restTemplate.setRequestFactory(requestFactory()); ​        // 设置错误处理器        restTemplate.setErrorHandler(new CustomResponseErrorHandler()); ​        // 设置消息转换器,例如处理 JSON 数据        restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); ​        return restTemplate;   } ​    @Bean    public HttpComponentsClientHttpRequestFactory requestFactory() {        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();        requestFactory.setConnectTimeout(5000); // 设置连接超时时间为5秒        requestFactory.setReadTimeout(5000); // 设置读取超时时间为5秒        return requestFactory;   } } ​ @Component public class CustomHeaderInterceptor implements ClientHttpRequestInterceptor { ​    @Override    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)            throws IOException {        HttpHeaders headers = request.getHeaders();        headers.add("X-Custom-Header", "Custom Value"); ​        return execution.execute(request, body);   } } ​ @Component public class CustomResponseErrorHandler extends DefaultResponseErrorHandler { ​    @Override    public void handleError(ClientHttpResponse response) throws IOException {        // 获取错误的 HTTP 状态码        HttpStatus statusCode = response.getStatusCode(); ​        if (statusCode.is4xxClientError()) {            // 处理客户端错误,例如 4xx 状态码            if (statusCode == HttpStatus.NOT_FOUND) {                throw new ResourceNotFoundException("Requested resource not found");           } else if (statusCode == HttpStatus.UNAUTHORIZED) {                throw new UnauthorizedException("Unauthorized access");           } else {                throw new RuntimeException("Client error occurred");           }       } else if (statusCode.is5xxServerError()) {            // 处理服务器错误,例如 5xx 状态码            if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR) {                throw new ServerErrorException("Internal server error");           } else if (statusCode == HttpStatus.SERVICE_UNAVAILABLE) {                throw new ServiceUnavailableException("Service unavailable");           } else {                throw new RuntimeException("Server error occurred");           }       } else {            // 其他错误处理            super.handleError(response);       }   } } ​ @Service public class ApiService { ​    private final RestTemplate restTemplate; ​    public ApiService(RestTemplate restTemplate) {        this.restTemplate = restTemplate;   } ​    public String fetchData(String url) {        try {            ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);            if (response.getStatusCode().is2xxSuccessful()) {                return response.getBody();           } else {                throw new RuntimeException("Request failed with status code: " + response.getStatusCodeValue());           }       } catch (RestClientException ex) {            // 处理异常情况            throw new RuntimeException("Request failed: " + ex.getMessage(), ex);       }   } } ​ public class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);        ApiService apiService = context.getBean(ApiService.class);        String data = apiService.fetchData("http://example.com/api/resource");        System.out.println("Response data: " + data);   } } ​ 复制代码

使用Apache HttpClient作为RestTemplate的底层实现:

org.springframework.boot spring-boot-starter-web ​ org.apache.httpcomponents httpclient ​ 复制代码 import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; ​ @Configuration @EnableConfigurationProperties public class CustomRestTemplateConfig { @Autowired(required = false) private ClientHttpRequestFactory requestFactory; ​ @Bean @ConditionalOnMissingBean public RestTemplate restTemplate() {   return new RestTemplate(getRequestFactory()); } ​ private ClientHttpRequestFactory getRequestFactory() {   if (this.requestFactory != null) {       return this.requestFactory;   } else {       return new HttpComponentsClientHttpRequestFactory(httpClient());   } } ​ @Bean @ConditionalOnMissingBean public HttpClient httpClient() {   return HttpClientBuilder.create().build(); } } ​ @SpringBootApplication(exclude = {RestTemplateAutoConfiguration.class}) public class MyApplication { // ... } ​ ​ 复制代码 Feign

Feign是一个声明式的HTTP客户端库,它简化了与RESTful服务进行交互的过程。

声明式API:Feign允许使用注解方式定义HTTP请求的接口,而无需编写具体的实现代码。通过编写接口,可以声明请求的URL、HTTP方法、请求参数等信息。

Feign提供了一系列注解来帮助定义请求接口,其中常用的注解包括:

@FeignClient:用于标识一个Feign客户端,指定服务的名称或URL。 @RequestMapping:类似于Spring MVC中的注解,用于指定请求的URL路径和HTTP方法。 @PathVariable:用于将URL路径中的变量绑定到方法参数。 @RequestParam:用于将请求参数绑定到方法参数。 @RequestBody:用于将请求体绑定到方法参数。 @RequestHeader:用于将请求头信息绑定到方法参数。 @FeignClient(name = "example-service") public interface ExampleFeignClient { @RequestMapping(value = "/api/resource/{id}", method = RequestMethod.GET) ResourceDTO getResource(@PathVariable("id") String id, @RequestParam("param") String param); @RequestMapping(value = "/api/resource", method = RequestMethod.POST) void createResource(@RequestBody ResourceDTO resource); @RequestMapping(value = "/api/resource/{id}", method = RequestMethod.PUT) void updateResource(@PathVariable("id") String id, @RequestBody ResourceDTO resource); @RequestMapping(value = "/api/resource/{id}", method = RequestMethod.DELETE) void deleteResource(@PathVariable("id") String id); } 复制代码

使用Feign声明式API时,还需要在Spring Boot应用程序的配置文件中启用Feign客户端。可以通过添加@EnableFeignClients注解或feign.enable=true的配置来实现。

内部集成了Ribbon和Hystrix:Feign内部集成了Netflix Ribbon和Netflix Hystrix,可以提供负载均衡和容错功能。Feign可以通过服务名称自动解析服务实例,并实现请求的负载均衡。

Ribbon负载均衡:Feign集成了Ribbon,可以通过服务名称自动解析服务实例,并实现请求的负载均衡。只需要在Feign客户端接口上使用@FeignClient注解指定服务名称,Feign就能够根据服务名称从服务注册中心(如Eureka)获取服务实例列表,并根据负载均衡策略选择一个实例发送请求。

Hystrix容错:Feign还集成了Hystrix,可以提供容错功能,防止远程调用的失败影响整个系统。Hystrix可以对远程调用进行熔断、降级和限流等操作,以保护系统的稳定性。在使用Feign时,可以通过配置feign.hystrix.enabled=true启用Hystrix支持,并在Feign客户端接口的方法上添加@HystrixCommand注解来定义容错逻辑。

熔断(Circuit Breaker)是Hystrix的核心功能之一,它可以在调用失败或达到一定的错误阈值时,打开断路器,阻止继续向该服务发起请求,从而快速失败,避免资源的浪费和雪崩效应。当断路器打开后,可以定义一个降级逻辑,返回一个预先设定的默认值或错误信息,以提供给调用方使用。 降级(Fallback)是在熔断状态下触发的操作,用于替代实际调用的逻辑,返回一个备选的响应。降级逻辑可以是从缓存中获取数据、返回静态数据、调用本地方法等。通过降级处理,可以在服务不可用时提供一个有意义的响应,保证系统的可用性。 限流(Rate Limiting)是指控制对服务的并发请求量或并发连接数,以防止系统过载。Hystrix提供了线程池隔离和信号量隔离两种限流模式,可以根据具体需求选择合适的限流策略。 org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-netflix-ribbon org.springframework.cloud spring-cloud-starter-netflix-hystrix 复制代码

在Spring Boot的启动类上添加@EnableFeignClients注解,启用Feign客户端:

@SpringBootApplication @EnableFeignClients public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } 复制代码

创建一个Feign客户端接口,使用@FeignClient注解指定服务名称,并使用@RibbonClient注解启用Ribbon负载均衡:

@FeignClient(name = "my-service") @RibbonClient(name = "my-service") public interface MyServiceClient { @GetMapping("/api/resource") String getResource(); } 复制代码

在配置文件中配置Ribbon的服务列表和Hystrix的相关配置:

# Ribbon配置 my-service: ribbon: listOfServers: example.com:8081, example.com:8082 # Hystrix配置 hystrix: command: default: execution.isolation.thread.timeoutInMilliseconds: 5000 复制代码

关于 Hystrix 请求超时需要注意的点:

Feign请求超时:如果Feign请求设置的超时时间较长,而另一个项目使用HTTP请求的超时时间较短,那么在请求另一个项目时,Feign请求可能会等待较长时间才会超时。这可能导致Feign请求的响应时间较长,从而影响系统的性能和吞吐量。 HTTP请求超时:如果Feign请求设置的超时时间较短,而另一个项目使用HTTP请求的超时时间较长,那么在请求另一个项目时,Feign请求可能会因为超时而中断,并触发熔断逻辑。此时,Feign可以根据熔断策略进行降级处理,例如返回默认值、调用备用接口等,以确保系统的可用性。

自动化的请求和响应转换:Feign通过集成Jackson等序列化库,自动处理请求和响应的数据转换。可以通过注解指定请求和响应的数据格式,例如JSON、XML等。

Feign会根据注解中指定的数据格式,自动将请求和响应的数据进行序列化和反序列化。默认情况下,Feign使用JSON作为数据的格式,但也可以通过其他注解(如@Produces和@Consumes)来指定其他的数据格式,例如XML。

@FeignClient(name = "example-service", url = "http://example.com") public interface ExampleFeignClient { @PostMapping(value = "/api/resource", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE) ExampleResponseDto postResource(@RequestBody ExampleRequestDto request); } @XmlRootElement public class ExampleRequestDto { // 请求数据的字段 } @XmlRootElement public class ExampleResponseDto { // 响应数据的字段 } @RestController public class ExampleController { @Autowired private ExampleFeignClient feignClient; @PostMapping("/call-example-service") public ExampleResponseDto callExampleService(@RequestBody ExampleRequestDto request) { return feignClient.postResource(request); } } 复制代码

consumes属性指定了请求数据的格式为XML,produces属性指定了响应数据的格式为XML。

使用@XmlRootElement注解标记了请求和响应的数据对象,以便进行XML序列化和反序列化。

定制化和扩展性:Feign提供了丰富的扩展点和自定义配置选项,允许根据需要进行定制化。可以自定义请求拦截器、错误处理器、编码器、解码器等,以满足特定的业务需求。

请求拦截器(Request Interceptors):可以实现RequestInterceptor接口,编写自定义的请求拦截器,用于在发送请求前进行额外的处理。例如,可以在请求头中添加认证信息、日志记录等。通过注册请求拦截器,可以在全局范围或特定的Feign客户端中应用自定义逻辑。 响应拦截器(Response Interceptors):类似于请求拦截器,可以实现ResponseInterceptor接口,编写自定义的响应拦截器,用于在处理响应数据前进行额外的处理。例如,可以对响应进行统一的处理、错误处理等。 错误处理器(Error Handlers):通过实现ErrorDecoder接口,可以自定义错误处理器来处理特定的错误情况。例如,可以根据不同的HTTP状态码或响应内容来处理异常,实现定制化的错误处理逻辑。 编码器(Encoders)和解码器(Decoders):Feign支持自定义编码器和解码器,用于处理请求和响应数据的编码和解码。可以实现Encoder接口和Decoder接口,根据需求选择合适的序列化和反序列化方式。这样可以适应不同的数据格式,如JSON、XML等。

请求拦截器(Request Interceptor):

public class CustomRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { // 在请求前进行额外处理,如添加请求头、认证信息等 template.header("Authorization", "Bearer token"); } } 复制代码

响应拦截器(Response Interceptor):

public class CustomResponseInterceptor implements ResponseInterceptor { @Override public void apply(Response response) { // 在处理响应前进行额外处理,如统一处理响应数据、错误处理等 if (response.status() == 404) { // 处理特定的HTTP状态码 throw new NotFoundException("Resource not found"); } } } 复制代码

错误处理器(Error Decoder):

public class CustomErrorDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { // 根据响应内容和状态码自定义异常处理逻辑 if (response.status() == 500) { return new CustomServerException("Server Error"); } else if (response.status() == 400) { return new CustomBadRequestException("Bad Request"); } return new FeignException(response.status(), response.reason()); } } 复制代码

编码器(Encoder)和解码器(Decoder):

public class CustomJsonEncoder implements Encoder { @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { // 自定义JSON编码逻辑,将对象转换为JSON格式的请求体 ObjectMapper objectMapper = new ObjectMapper(); try { String jsonBody = objectMapper.writeValueAsString(object); template.body(jsonBody, MediaType.APPLICATION_JSON_VALUE); } catch (JsonProcessingException e) { throw new EncodeException("Error encoding object to JSON", e); } } } public class CustomJsonDecoder implements Decoder { @Override public Object decode(Response response, Type type) throws DecodeException, FeignException { // 自定义JSON解码逻辑,将响应体转换为对象 ObjectMapper objectMapper = new ObjectMapper(); try { return objectMapper.readValue(response.body().asInputStream(), objectMapper.constructType(type)); } catch (IOException e) { throw new DecodeException("Error decoding JSON response", e); } } } 复制代码 @Configuration public class FeignConfig { @Bean public CustomRequestInterceptor customRequestInterceptor() { return new CustomRequestInterceptor(); } @Bean public CustomResponseInterceptor customResponseInterceptor() { return new CustomResponseInterceptor(); } @Bean public CustomErrorDecoder customErrorDecoder() { return new CustomErrorDecoder(); } @Bean public CustomJsonEncoder customJsonEncoder() { return new CustomJsonEncoder(); } @Bean public CustomJsonDecoder customJsonDecoder() { return new CustomJsonDecoder(); } } @FeignClient(name = "example-service", configuration = FeignConfig.class) public interface ExampleClient { // 接口方法定义 } 复制代码

整合Spring Cloud:Feign是Spring Cloud生态系统的一部分,可以与其他Spring Cloud组件无缝集成,例如Eureka、Zuul等。它可以与Spring Boot应用程序无缝配合使用,简化微服务架构中的服务间通信。

HTTP 客户端库 Apache HttpClient

强大的功能:Apache HttpClient支持HTTP/1.1协议的各种特性,包括连接管理、请求和响应拦截、重定向、认证、代理等。它可以执行各种类型的HTTP请求,如GET、POST、PUT、DELETE等,并支持多种数据格式的处理,如JSON、XML等。

import org.apache.http.HttpHost; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; ​ @Configuration public class HttpClientConfig { ​    @Value("${http.maxTotalConnections}")    private int maxTotalConnections; ​    @Value("${http.defaultMaxPerRoute}")    private int defaultMaxPerRoute; ​    @Value("${http.connectionRequestTimeout}")    private int connectionRequestTimeout; ​    @Value("${http.connectTimeout}")    private int connectTimeout; ​    @Value("${http.socketTimeout}")    private int socketTimeout; ​    @Value("${http.proxyHost}")    private String proxyHost; ​    @Value("${http.proxyPort}")    private int proxyPort; ​    @Bean    public CloseableHttpClient httpClient() {        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();        connectionManager.setMaxTotal(maxTotalConnections);        connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute); ​        HttpHost proxy = new HttpHost(proxyHost, proxyPort); ​        RequestConfig requestConfig = RequestConfig.custom()               .setConnectionRequestTimeout(connectionRequestTimeout)               .setConnectTimeout(connectTimeout)               .setSocketTimeout(socketTimeout)               .setProxy(proxy)               .build(); ​        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();        // 设置认证信息 ​        return HttpClientBuilder.create()               .setConnectionManager(connectionManager)               .setDefaultRequestConfig(requestConfig)               .setDefaultCredentialsProvider(credentialsProvider)               .build();   } } ​ 复制代码

通过在HttpClient的配置中设置代理信息,可以将请求通过代理服务器发送出去,而不是直接使用本机的IP和端口。在示例代码中,使用了HttpHost来定义代理的主机和端口,并将其设置到RequestConfig中。这样配置后,HttpClient会将请求发送到指定的代理服务器,然后由代理服务器转发请求到目标服务器,实现了通过代理发送请求的功能。

高级配置选项:Apache HttpClient提供了丰富的配置选项,可以根据需要进行定制化。可以设置连接池的大小、超时时间、重试策略等。它还支持连接的复用和持久连接,以提高性能和效率。

import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class HttpClientConfig { @Bean public CloseableHttpClient httpClient() { PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(100); connectionManager.setDefaultMaxPerRoute(20); RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(5000) .setSocketTimeout(5000) .build(); ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> { HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && param.equalsIgnoreCase("timeout")) { return Long.parseLong(value) * 1000; } } return 30 * 1000; }; return HttpClients.custom() .setConnectionManager(connectionManager) .setDefaultRequestConfig(requestConfig) .setKeepAliveStrategy(keepAliveStrategy) .build(); } } 复制代码

请求和响应处理:Apache HttpClient提供了灵活的请求和响应处理机制。可以通过设置请求头、请求参数、请求体等来定制请求。对于响应,可以获取响应的状态码、响应头、响应体等信息,并对其进行处理。

import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.clienthods.*; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.io.IOException; public class HttpClientExample { public static void main(String[] args) throws IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); // 设置请求头 String token = "your-token"; String authorizationHeader = "Bearer " + token; // 发起GET请求 HttpGet httpGet = new HttpGet("http://example.com/api/resource"); httpGet.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); HttpResponse getResponse = httpClient.execute(httpGet); printResponse(getResponse); // 发起POST请求 HttpPost httpPost = new HttpPost("http://example.com/api/resource"); httpPost.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); httpPost.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); String requestBody = "{"key": "value"}"; httpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); HttpResponse postResponse = httpClient.execute(httpPost); printResponse(postResponse); // 发起DELETE请求 HttpDelete httpDelete = new HttpDelete("http://example.com/api/resource/123"); httpDelete.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); HttpResponse deleteResponse = httpClient.execute(httpDelete); printResponse(deleteResponse); // 发起PUT请求 HttpPut httpPut = new HttpPut("http://example.com/api/resource/123"); httpPut.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); httpPut.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); String requestBody = "{"key": "value"}"; httpPut.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); HttpResponse putResponse = httpClient.execute(httpPut); printResponse(putResponse); // 关闭HttpClient httpClient.close(); } private static void printResponse(HttpResponse response) throws IOException { System.out.println("Status Code: " + response.getStatusLine().getStatusCode()); System.out.println("Response Body: " + EntityUtils.toString(response.getEntity())); // 可以根据需要处理响应的其他信息 } } 复制代码

并发执行:Apache HttpClient支持并发执行多个HTTP请求,通过使用连接池和线程池来提高并发性能。可以同时发送多个请求,并异步地获取它们的响应。

Apache HttpClient提供的异步执行机制来实现并发执行多个请求。通过将请求提交到线程池中,可以并发地发送请求,并通过回调或Future对象来获取每个请求的响应结果。

CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault(); // 创建多个HttpGet请求 HttpGet request1 = new HttpGet("http://api.example.com/resource1"); HttpGet request2 = new HttpGet("http://api.example.com/resource2"); HttpGet request3 = new HttpGet("http://api.example.com/resource3"); // 提交请求到线程池,并异步执行 Future future1 = httpClient.execute(request1, null); Future future2 = httpClient.execute(request2, null); Future future3 = httpClient.execute(request3, null); // 获取每个请求的响应结果 HttpResponse response1 = future1.get(); HttpResponse response2 = future2.get(); HttpResponse response3 = future3.get(); // 处理响应结果 // ... // 关闭HttpClient httpClient.close(); 复制代码

有时候我们遇到一个场景:

下一个请求需要使用到上一个请求返回类的参数,虽然我们可以通过分两次请求,但是这样写代码不好看,并且不清晰。我们可以通过使用异步请求和回调函数的方式来处理。

CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault(); // 第一个请求 HttpGet request1 = new HttpGet("http://api.example.com/resource1"); Future future1 = httpClient.execute(request1, null); // 处理第一个请求的响应结果 future1.thenApply(response1 -> { // 解析第一个请求的响应结果 String result1 = parseResponse(response1); // 第二个请求,将第一个请求的结果作为参数 HttpGet request2 = new HttpGet("http://api.example.com/resource2?param=" + result1); Future future2 = httpClient.execute(request2, null); // 处理第二个请求的响应结果 future2.thenApply(response2 -> { // 解析第二个请求的响应结果 String result2 = parseResponse(response2); // 执行下一步操作,使用第二个请求的结果 performNextStep(result2); return null; }); return null; }); // 等待所有请求执行完成 CompletableFuture.allOf(future1).join(); // 关闭HttpClient httpClient.close(); 复制代码 实战: import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //创建一个配置类 HttpClientConfig,用于配置 HttpClient 的相关参数: @Configuration public class HttpClientConfig { private static final int TIMEOUT = 5000; // 请求超时时间,单位:毫秒 @Bean public CloseableHttpClient httpClient() { RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(TIMEOUT) .setConnectionRequestTimeout(TIMEOUT) .setSocketTimeout(TIMEOUT) .build(); return HttpClients.custom() .setDefaultRequestConfig(requestConfig) .build(); } } //创建一个使用 HttpClient 的服务类 HttpService,用于发起 HTTP 请求: import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.clienthods.HttpGet; import org.apache.http.clienthods.HttpUriRequest; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class HttpService { private final CloseableHttpClient httpClient; @Autowired public HttpService(CloseableHttpClient httpClient) { this.httpClient = httpClient; } public String get(String url) throws Exception { HttpGet httpGet = new HttpGet(url); HttpResponse response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); return EntityUtils.toString(entity); } } //需要使用 HttpClient 的地方,注入 HttpService 并调用其方法即可: import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application implements CommandLineRunner { private final HttpService httpService; @Autowired public Application(HttpService httpService) { this.httpService = httpService; } public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Override public void run(String... args) throws Exception { String response = httpService.get("http://api.example.com/resource"); System.out.println("Response: " + response); } } 复制代码 OkHttp

简洁的 API:OkHttp 提供了易于使用的 API,使发送 HTTP 请求变得简单和直观。可以使用链式调用的方式设置请求的 URL、方法、请求头、请求体等信息。

import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; ​ public class OkHttpExample {    public static void main(String[] args) throws Exception {        OkHttpClient client = new OkHttpClient(); ​        Request request = new Request.Builder()               .url("https://api.example.com/data")               .get()               .addHeader("Authorization", "Bearer token")               .build(); ​        Response response = client.newCall(request).execute();        String responseBody = response.body().string(); ​        System.out.println("Response: " + responseBody);   } } ​ //发送POST请求 OkHttpClient client = new OkHttpClient(); ​ Request request = new Request.Builder()       .url("https://api.example.com/users")       .post(RequestBody.create(MediaType.parse("application/json"), requestBodyJson))       .build(); ​ Response response = client.newCall(request).execute(); ​ ​ //发送DELETE请求 OkHttpClient client = new OkHttpClient(); ​ Request request = new Request.Builder()       .url("https://api.example.com/users/123")       .delete()       .build(); ​ Response response = client.newCall(request).execute(); ​ ​ //发送PUT请求: OkHttpClient client = new OkHttpClient(); ​ Request request = new Request.Builder()       .url("https://api.example.com/users/123")       .put(RequestBody.create(MediaType.parse("application/json"), requestBodyJson))       .build(); ​ Response response = client.newCall(request).execute(); ​ 复制代码

高效性能:OkHttp 使用了连接池和复用连接的技术,可以有效地管理和重用连接,减少网络请求的延迟和资源消耗。它还支持异步请求和流式传输,以提高并发性能。

异步请求

OkHttpClient client = new OkHttpClient(); ​ Request request = new Request.Builder()       .url("https://api.example.com/data")       .build(); ​ client.newCall(request).enqueue(new Callback() {    @Override    public void onResponse(Call call, Response response) throws IOException {        // 处理响应结果        String responseBody = response.body().string();        System.out.println("Response: " + responseBody);   } ​    @Override    public void onFailure(Call call, IOException e) {        // 处理请求失败        e.printStackTrace();   } }); ​ 复制代码

同步请求:

OkHttpClient client = new OkHttpClient(); ​ Request request = new Request.Builder()       .url("https://api.example.com/data")       .build(); ​ try (Response response = client.newCall(request).execute()) {    // 处理响应结果    String responseBody = response.body().string();    System.out.println("Response: " + responseBody); } catch (IOException e) {    // 处理请求失败    e.printStackTrace(); } ​ 复制代码

文件下载:

OkHttpClient client = new OkHttpClient(); ​ Request request = new Request.Builder()       .url("https://example.com/file.pdf")       .build(); ​ try (Response response = client.newCall(request).execute()) {    // 将响应结果写入文件    InputStream inputStream = response.body().byteStream();    FileOutputStream outputStream = new FileOutputStream("downloaded_file.pdf");    byte[] buffer = new byte[4096];    int bytesRead;    while ((bytesRead = inputStream.read(buffer)) != -1) {        outputStream.write(buffer, 0, bytesRead);   }    outputStream.close(); } catch (IOException e) {    // 处理请求失败    e.printStackTrace(); } ​ 复制代码

请求和响应拦截器:OkHttp 提供了拦截器的机制,允许在发送请求和接收响应的过程中对其进行处理和修改。可以添加自定义的拦截器来实现身份验证、请求重试、日志记录等功能。

拦截器使用Interceptor接口进行定义,它包含了两个方法:intercept()和chain.proceed()。

记录请求和响应的日志:

class LoggingInterceptor implements Interceptor {    @Override    public Response intercept(Chain chain) throws IOException {        Request request = chain.request(); ​        long startTime = System.nanoTime();        System.out.println("Sending request: " + request.url()); ​        Response response = chain.proceed(request); ​        long endTime = System.nanoTime();        System.out.println("Received response for " + request.url() + " in " + ((endTime - startTime) / 1e6) + " ms"); ​        return response;   } } ​ OkHttpClient client = new OkHttpClient.Builder()       .addInterceptor(new LoggingInterceptor())       .build(); ​ 复制代码

身份验证拦截器示例:

class AuthInterceptor implements Interceptor {    private String authToken; ​    public AuthInterceptor(String authToken) {        this.authToken = authToken;   } ​    @Override    public Response intercept(Chain chain) throws IOException {        Request originalRequest = chain.request(); ​        // 添加身份验证的请求头        Request authenticatedRequest = originalRequest.newBuilder()               .header("Authorization", authToken)               .build(); ​        return chain.proceed(authenticatedRequest);   } } 复制代码

请求重试拦截器示例:

class RetryInterceptor implements Interceptor {    private int maxAttempts;    private int currentAttempt = 0; ​    public RetryInterceptor(int maxAttempts) {        this.maxAttempts = maxAttempts;   } ​    @Override    public Response intercept(Chain chain) throws IOException {        Request request = chain.request();        Response response = null; ​        while (currentAttempt < maxAttempts) {            try {                response = chain.proceed(request);                break;           } catch (IOException e) {                // 请求失败,进行重试                currentAttempt++;                if (currentAttempt >= maxAttempts) {                    throw e; // 达到最大重试次数,抛出异常               }           }       } ​        return response;   } } ​ 复制代码

支持同步和异步请求:OkHttp 提供了同步和异步两种方式发送请求。通过同步方式发送请求,可以直接获取到响应的结果;而通过异步方式发送请求,可以注册回调来处理响应的结果,这对于处理大量并发请求非常有用。

enqueue(Callback callback): 这是最常用的发送异步请求的方法。可以通过实现Callback接口来处理请求的响应结果和错误情况。

OkHttpClient client = new OkHttpClient(); ​ Request request = new Request.Builder()       .url("https://api.example.com/users")       .build(); ​ client.newCall(request).enqueue(new Callback() {    @Override    public void onResponse(Call call, Response response) throws IOException {        // 处理响应结果        String responseBody = response.body().string();        // ...   } ​    @Override    public void onFailure(Call call, IOException e) {        // 处理异常        e.printStackTrace();   } }); ​ 复制代码

enqueue(Callback callback, Executor executor): 这个方法与上述方法类似,但是允许指定一个Executor来执行回调。这样可以在指定的线程池中处理回调,例如使用Executors.newCachedThreadPool()来创建线程池。

OkHttpClient client = new OkHttpClient(); ​ Request request = new Request.Builder()       .url("https://api.example.com/users")       .build(); ​ Executor executor = Executors.newCachedThreadPool(); client.newCall(request).enqueue(new Callback() {    // ... ​ }, executor); ​ 复制代码

newWebSocket(Request request, WebSocketListener listener): 如果需要进行WebSocket通信,可以使用此方法来创建一个WebSocket连接。需要实现WebSocketListener接口来处理WebSocket的事件和消息。

OkHttpClient client = new OkHttpClient(); ​ Request request = new Request.Builder()       .url("wss://api.example.com/socket")       .build(); ​ WebSocketListener listener = new WebSocketListener() {    // ... ​ }; ​ WebSocket webSocket = client.newWebSocket(request, listener); ​ 复制代码

支持WebSocket:OkHttp 还支持 WebSocket 协议,可以使用 OkHttp 来创建 WebSocket 连接并进行双向通信。

使用OkHttp创建WebSocket连接:

OkHttpClient client = new OkHttpClient(); ​ ​ Request request = new Request.Builder()   .url("ws://example.com/socket")   .build(); ​ WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {    @Override    public void onOpen(WebSocket webSocket, Response response) {        // WebSocket连接已打开,可以进行通信   } ​    @Override    public void onMessage(WebSocket webSocket, String text) {        // 接收到WebSocket服务器发送的消息   } ​    @Override    public void onClosed(WebSocket webSocket, int code, String reason) {        // WebSocket连接已关闭   } ​    @Override    public void onFailure(WebSocket webSocket, Throwable t, Response response) {        // WebSocket连接出现异常   } }); ​ // 发送文本消息 webSocket.send("Hello, server!"); ​ // 关闭WebSocket连接 webSocket.close(1000, "Goodbye, server!"); ​ 复制代码

定制化和扩展性:OkHttp 提供了丰富的定制化选项,可以根据需要进行配置和扩展。可以自定义连接池、超时设置、缓存策略等,以满足特定的需求。

连接池定制化:可以通过 ConnectionPool 类设置连接池的最大连接数、保持时间等参数,以控制连接的复用和管理。例如: ConnectionPool connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.MILLISECONDS); OkHttpClient client = new OkHttpClient.Builder()   .connectionPool(connectionPool)   .build(); 复制代码 超时设置:可以使用 OkHttpClient.Builder 中的方法来设置连接超时、读取超时和写入超时等参数。例如: OkHttpClient client = new OkHttpClient.Builder()   .connectTimeout(10, TimeUnit.SECONDS)   .readTimeout(30, TimeUnit.SECONDS)   .writeTimeout(30, TimeUnit.SECONDS)   .build(); 复制代码 缓存策略定制化:OkHttp 支持 HTTP 缓存,并提供了 CacheControl 类来设置缓存策略。可以根据具体需求设置缓存的有效期、条件等。例如: CacheControl cacheControl = new CacheControl.Builder()   .maxAge(1, TimeUnit.DAYS)   .build(); Request request = new Request.Builder()   .url("http://example.com")   .cacheControl(cacheControl)   .build(); 复制代码 拦截器扩展:可以使用 Interceptor 接口来定义自定义的请求和响应拦截器,以对请求和响应进行处理和修改。例如,实现一个自定义的日志拦截器: Interceptor loggingInterceptor = new Interceptor() {   @Override   public Response intercept(Chain chain) throws IOException {       Request request = chain.request();       long startTime = System.nanoTime();       System.out.println("Sending request: " + request.url());       Response response = chain.proceed(request);       long endTime = System.nanoTime();       long duration = endTime - startTime;       System.out.println("Received response for: " + response.request().url() + " in " + duration + " nanoseconds");       return response;   } }; ​ OkHttpClient client = new OkHttpClient.Builder()   .addInterceptor(loggingInterceptor)   .build(); 复制代码


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3